//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>

#include <unistd.h>
#include <dlfcn.h>

#include <math.h>

#include "apcluster.h"
#include "rutines.h"
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
#define LOCATION_APA "./apclusterunix64.so"
#define APA_FUNCTION "apclusterf32"
#define LENGTH_LINGO 4
#define USE_DISTANCE 1
#define MODE_NORMAL  0
#define MODE_FAST    1
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int callback (float* curr_a, float* curr_r, int N, int* curr_idx, int I, double curr_netsim, int iter) {
  
  if (curr_netsim == -HUGE_VAL) 
    printf("%d\t%s\n", iter, "NaN");    // cannot calculate netsim
  else 
    printf("%d\t%f\n", iter, curr_netsim);
  
  return (EXIT_SUCCESS);                // 0 = continue apcluster 
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
float obtainLingoSim (char *string1, char *string2) {

  long length1 = strlen(string1);
  long length2 = strlen(string2);
  long i;
  long j;
  long k;
  long z;
  long higher = ((length1 > length2)?length1:length2);

  char tmpString1 [length1 + 1];
  char tmpString2 [length2 + 1];
  char compare    [LENGTH_LINGO + 1];

  for (i = 0, j = 0, k = 0; i < higher; i++) {
    if (i < length1) {
      if      (string1[i] == 'l') tmpString1[j - 1] = 'L';
      else if (string1[i] == 'r') tmpString1[j - 1] = 'R';
      else                        tmpString1[j++]   = string1[i];
    }
    if (i < length2) {
      if      (string2[i] == 'l') tmpString2[k - 1] = 'L';
      else if (string2[i] == 'r') tmpString2[k - 1] = 'R';
      else                        tmpString2[k++]   = string2[i];
    }
  }
  
  tmpString1[j] = '\0'; 
  tmpString2[k] = '\0';
  
  j -= (LENGTH_LINGO - 1); 
  k -= (LENGTH_LINGO - 1);
  
  long   L               = j + k; 
  long   zHigher         = ((j > k)?j:k);

  float  similarityValue = 0;
  float  Nai;
  float  Nbi;
  
  bool   analize;

  for (i = 0; i < j + k; i++) {

    analize = true;

    Nai = 0;
    Nbi = 0;

    if (i < j) {

      memcpy(compare, tmpString1 + i, LENGTH_LINGO);

      for (z = 0; z < zHigher; z++) {
        if (z < j && !memcmp(compare, tmpString1 + z, LENGTH_LINGO)) {
          if (z < i) {
            analize = false;
            break;
          }
          Nai++;
        }
        if (z < k && !memcmp(compare, tmpString2 + z, LENGTH_LINGO)) {
          Nbi++;
        }
      }
    }
    else {

      memcpy(compare, tmpString2 + i - j, LENGTH_LINGO);

      for (z = 0; z < zHigher; z++) {
        if (z < j && !memcmp(compare, tmpString1 + z, LENGTH_LINGO)) {
          analize = false;
          break;
        }
        if (z < k && !memcmp(compare, tmpString2 + z, LENGTH_LINGO)) {
          if (z < i - j) {
            analize = false;
            break;
          }
          Nbi++;
        }
      }
    }

    if (analize == true) similarityValue += ((((Nai - Nbi) >= 0)?((Nai - Nbi)):((Nbi - Nai)))/(Nai + Nbi));
    else                 L--;
  }

  return 1 - similarityValue/L;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
float obtainLingoSimFast (char* string1, char* string2) {

  char  tmpString1 [strlen(string1) + 1];
  char  tmpString2 [strlen(string2) + 1];
  char  compare   [LENGTH_LINGO + 1];
  
  long  i;
  long  j                  = strlen(string1) - (LENGTH_LINGO - 1);;
  long  k                  = strlen(string2) - (LENGTH_LINGO - 1);
  long  z;
  long  L                  = j + k; 
  long  zHigher            = ((j > k)?j:k);
  
  float similarityValue    = 0;
  float Nai;
  float Nbi;
  
  bool   analize;

  for (i = 0; i < j + k; i++) {

    analize = true;

    Nai = 0; 
    Nbi = 0;

    if (i < j) {

      memcpy(compare, tmpString1 + i, LENGTH_LINGO);

      for (z = 0; z < zHigher; z++) {
        if (z < j && !memcmp(compare, tmpString1 + z, LENGTH_LINGO)) {
          if (z < i) {
            analize = false;
            break;
          }
          Nai++;
        }
        if (z < k && !memcmp(compare, tmpString2 + z, LENGTH_LINGO)) {
          Nbi++;
        }
      }
    }
    else {

      memcpy(compare, tmpString2 + i - j, LENGTH_LINGO);

      for (z = 0; z < zHigher; z++) {
        if (z < j && !memcmp(compare, tmpString1 + z, LENGTH_LINGO)) {
          analize = false;
          break;
        }
        if (z < k && !memcmp(compare, tmpString2 + z, LENGTH_LINGO)) {
          if (z < i - j) {
            analize = false;
            break;
          }
          Nbi++;
        }
      }
    }

    if (analize == true) similarityValue += ((((Nai - Nbi) >= 0)?((Nai - Nbi)):((Nbi - Nai)))/(Nai + Nbi));
    else                 L--;
  }

  return 1 - similarityValue/L;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void getSij (float* sij, int* size, char* smiles[], char* ids[], int mode) {
 
  char  buffer [MAXSTR];
  char  smile  [MAXSTR];
  char  id     [MAXSTR];
  
  int   n       = 0, m = 0;
  int   counter = 0;
  
  float lsim;
  float sum;
  
  while ((n = readString(buffer)) != 0 && counter < *size) {
    sscanf(buffer, "%s %s", smile, id);
    smile[strlen(smile)] = '\0';
    id[strlen(id)]       = '\0';
    memcpy((smiles[counter] = malloc ((strlen(smile) + 1) * sizeof(char))), smile, strlen(smile));
    memcpy((ids[counter++]  = malloc ((strlen(id)    + 1) * sizeof(char))), id,    strlen(id));
  }

  if (*size != counter) {  
    if (sij) free(sij);
    *size = counter;
    sij   = (float*) malloc((counter * counter) * sizeof(float));
  }
  
  for (n = 0; n < *size; n++)
    for (m = n; m < *size; m++) {
      if (n == m) { sij[(n * (*size)) + m] = 0; continue; }
      lsim = (mode == MODE_NORMAL)?obtainLingoSim(smiles[n], smiles[m]):obtainLingoSimFast(smiles[n], smiles[m]);
      sij[(n * (*size)) + m] = sij[(m * (*size)) + n] = (USE_DISTANCE)?(log10(lsim)*10):(lsim - 1);
    }
  
  for (n = 0; n < *size; n++) {
    sum = 0;
    for (m = n; m < *size; m++)
      sum += sij[(n * (*size)) + m];
    sij[(n * (*size)) + n] = sum/((*size) - 1);
  }
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int main (int argc, char** argv) {

  if (argc < 2)
    error("Missing Arguments");
  
  int (*apclusterf32)(float*, unsigned int*, unsigned int*, unsigned int, int*, double*, APOPTIONS*); 
  
  int           m         = atoi(argv[1]);  // data points, similarities & preferences 
  int           N         = m*m;            // data points, similarities & preferences 
  int           iter;                       // memory for returning clusters of the data points 
  int           k;                          // memory for returning clusters of the data points 
  int           a;
  int           b;
  int           mode      = MODE_NORMAL;
  
  int*          idx       = 0;              // memory for returning clusters of the data points 
  
  double        netsim    = 0.0;
  
  float*        sij       = (float*)        malloc(N * sizeof(float));

  unsigned int* i         = NULL;
  unsigned int* j         = NULL;
  
  APOPTIONS     apoptions = {0};
  
  void*         dlh       = NULL;
  
  char*         smiles [m];
  char*         ids    [m];
  char*         err;
  char          buffer [MAXSTR];
  
  for (a = 0; a < ((m > argc)?m:argc); a++) {
    if (a < argc) {
      if      (!memcmp(argv[a], "fast", strlen("fast"))) mode = MODE_FAST;
      else if (!memcmp(argv[a], "fork", strlen("fork"))) kill(getppid(), SIGUSR1);
    }
    if (a < m) smiles[a] = ids[a] = NULL;
  }

  getSij(sij, &m, smiles, ids, mode);
  
  if (m != atoi(argv[1])) N = m*m;

  i = (unsigned int*) malloc(N * sizeof(unsigned int));
  j = (unsigned int*) malloc(N * sizeof(unsigned int));
  
  for (a = 0; a < m; a++)
    for (b = 0; b < m; b++) {
      i[(a*m) + b] = a;
      j[(a*m) + b] = b;
    }
    
  if (!(dlh = dlopen(LOCATION_APA, RTLD_LAZY)))
    error(dlerror());
  
  apclusterf32 = (int(*)(float*, unsigned int*, unsigned int*, unsigned int, int*, double*, APOPTIONS*)) dlsym(dlh, APA_FUNCTION);
  
  if ((err = dlerror()) != NULL)
    error(err);

  apoptions.cbSize              = sizeof(APOPTIONS);
  apoptions.lambda              = 0.9;
  apoptions.minimum_iterations  = 1;
  apoptions.converge_iterations = 100;
  apoptions.maximum_iterations  = 1000;
  apoptions.nonoise             = 0;
  apoptions.progress            = NULL;
  apoptions.progressf           = callback;
  
  iter = (*apclusterf32)(sij, i, j, N, idx = (int*) calloc(m, sizeof (*idx)), &netsim, &apoptions); 

  if (iter > 0) 
    for (k = 0; k < m; k++) {
      sprintf(buffer, "%-15s %-15s\n", ids[k], ids[idx[k]]);
      write(STDOUT_FILENO, buffer, strlen(buffer));
    }
  else {
    sprintf(buffer, "Error code: %d", iter);
    writeString(buffer, NULL);
  }

  dlclose(dlh);
  
  for (a = 0; a < m; a++) {
    if (smiles[a]) free(smiles[a]);
    if (ids[a])    free(ids[a]);
  }
  
  if (sij) free(sij);                        
  if (i)   free(i);                        
  if (j)   free(j);                        
  
  if (idx) free(idx);                        
  
  return EXIT_SUCCESS;
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
